/*
 * Copyright (c) 2010, Jim Hollinger
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Jim Hollinger nor the names of its contributors
 *     may be used to endorse or promote products derived from this
 *     software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <string>

#if defined (WIN32)
# include "ActiveSocket.h"
# include "PassiveSocket.h"
#else
# include "clsocket/ActiveSocket.h"
# include "clsocket/PassiveSocket.h"
#endif

#include "debughelper/debughelper.h"
#include "logger/logger.h"
#include "datachannel/clsocketchannel.h"


using namespace datachannel;


/**
 ****************************************************************************
 * @brief  Constructor
 ****************************************************************************
**/
CLSocketChannel::CLSocketChannel(logger::Logger *logger)
    : SocketChannel(logger), activeSocket(NULL), passiveSocket(NULL) {
}


/**
 ****************************************************************************
 * @brief  Destructor
 ****************************************************************************
**/
CLSocketChannel::~CLSocketChannel() {
    if (activeSocket != NULL) {
        activeSocket->Close();
        CActiveSocket::DestroySocket(activeSocket);
        activeSocket = NULL;
    }
    if (passiveSocket != NULL) {
        passiveSocket->Close();
        CPassiveSocket::DestroySocket(passiveSocket);
        passiveSocket = NULL;
    }
}


/**
 ****************************************************************************
 * @brief  Opens the socket.
 *
 * @param  descriptor  IP address to open
 *                      ip_address:port[:active|:passive][:tcp|:udp]
 *                      default = passive + udp
 ****************************************************************************
**/
bool CLSocketChannel::open(const std::string &descriptor) {
    close();
    processDescriptor(descriptor);
    if (mode == ACTIVE) {
        if (type == TCP) {
            activeSocket =
                CActiveSocket::CreateSocket(CSimpleSocket::SocketTypeTcp);
        } else {  // UDP
            activeSocket =
                CActiveSocket::CreateSocket(CSimpleSocket::SocketTypeUdp);
        }
        if (activeSocket == NULL) {
            log->error(
                "CLSocketChannel: Unable to create socket to %s:%d:%s",
                ip_address.c_str(), ip_port, typeToString(type));
        } else {
            if (activeSocket->Initialize()) {
                if (activeSocket->Open(
                    reinterpret_cast<const uint8 *>(ip_address.c_str()),
                    static_cast<int16>(ip_port))) {

                    log->info(
                        "CLSocketChannel opened socket to %s:%d:%s",
                        ip_address.c_str(), ip_port, typeToString(type));

                    // disable blocking
                    if (!activeSocket->SetNonblocking()) {
                        log->error(
                            "CLSocketChannel: Failed to disable blocking on %s:%d:%s",
                            ip_address.c_str(), ip_port, typeToString(type));
                    }
                } else {
                    log->error(
                        "CLSocketChannel: Failed to open socket to %s:%d:%s",
                        ip_address.c_str(), ip_port, typeToString(type));
                }
            } else {
                log->error(
                    "CLSocketChannel: Failed to initialize socket to %s:%d:%s",
                    ip_address.c_str(), ip_port, typeToString(type));
            }
        }
    } else {  // PASSIVE
        if (type == TCP) {
            passiveSocket =
                CPassiveSocket::CreateSocket(CSimpleSocket::SocketTypeTcp);
        } else {  // UDP
            passiveSocket =
                CPassiveSocket::CreateSocket(CSimpleSocket::SocketTypeUdp);
        }
        if (passiveSocket == NULL) {
            log->error(
                "CLSocketChannel: Unable to create socket for %s:%d:%s",
                ip_address.c_str(), ip_port, typeToString(type));
        } else {
            if (passiveSocket->Initialize()) {
                if (passiveSocket->Listen(
                    reinterpret_cast<const uint8 *>(ip_address.c_str()),
                    static_cast<int16>(ip_port))) {

                    log->info(
                        "CLSocketChannel listening on socket %s:%d:%s",
                        ip_address.c_str(), ip_port, typeToString(type));

                    // disable blocking
                    if (!passiveSocket->SetNonblocking()) {
                        log->error(
                            "CLSocketChannel: Failed to disable blocking on %s:%d:%s",
                            ip_address.c_str(), ip_port, typeToString(type));
                    }

                    // enable broadcast reception
                    if (!passiveSocket->SetBroadcast(true)) {
                        log->error(
                            "CLSocketChannel: Failed to enable broadcast reception on %s:%d:%s",
                            ip_address.c_str(), ip_port, typeToString(type));
                    }
                } else {
                    log->error(
                        "CLSocketChannel: Failed to open socket for %s:%d:%s",
                        ip_address.c_str(), ip_port, typeToString(type));
                }
            } else {
                log->error(
                    "CLSocketChannel: Failed to initialize socket for %s:%d:%s",
                    ip_address.c_str(), ip_port, typeToString(type));
            }
        }
    }

    return (activeSocket != NULL) || (passiveSocket != NULL);
}


/**
 ****************************************************************************
 * @brief  Closes the socket.
 ****************************************************************************
**/
bool CLSocketChannel::close() {
    bool flag = true;

    if (passiveSocket != NULL) {
        flag &= passiveSocket->Close();
        CPassiveSocket::DestroySocket(passiveSocket);
        passiveSocket = NULL;
        if (activeSocket == NULL) {
            log->info(
                "CLSocketChannel closed socket for %s:%d:%s",
                ip_address.c_str(), ip_port, typeToString(type));
        }
    }
    if (activeSocket != NULL) {
        flag &= activeSocket->Close();
        CActiveSocket::DestroySocket(activeSocket);
        activeSocket = NULL;
        log->info(
            "CLSocketChannel closed socket to %s:%d:%s",
            ip_address.c_str(), ip_port, typeToString(type));
    }

    return flag;
}


/**
 ****************************************************************************
 * @brief  Reads from the socket.
 ****************************************************************************
**/
int CLSocketChannel::read(void *buffer, int maxCount) {
    int numRead = 0;

    if (mode == ACTIVE) {
        if (activeSocket == NULL) {
            log->warn(
                "CLSocketChannel.read: %s:%d:%s not opened",
                ip_address.c_str(), ip_port, typeToString(type));
        } else {
            numRead = activeSocket->Receive(maxCount);
            if (numRead > 0) {
                if (numRead > maxCount) {
                    log->debug(
                        "CLSocketChannel.read received %d/%d bytes from %s:%d:%s",
                        numRead, maxCount,
                        ip_address.c_str(), ip_port, typeToString(type));
                    numRead = maxCount;
                } else {
                    log->debug(
                        "CLSocketChannel.read received %d bytes from %s:%d:%s",
                        numRead,
                        ip_address.c_str(), ip_port, typeToString(type));
                }
                memcpy(buffer, activeSocket->GetData(), numRead);
            }
        }
    } else {  // PASSIVE
        if (passiveSocket == NULL) {
            log->warn(
                "CLSocketChannel.read: %s:%d:%s not opened",
                ip_address.c_str(), ip_port, typeToString(type));
        } else {
            if (type == TCP) {
                if (activeSocket == NULL) {
                    activeSocket = passiveSocket->Accept();
                    if (activeSocket != NULL) {
                        ip_address = reinterpret_cast<char *>
                            (activeSocket->GetClientAddr());
                        ip_port = activeSocket->GetClientPort();
                        log->info(
                    "CLSocketChannel.read accepted new conection from %s:%d:%s",
                            ip_address.c_str(), ip_port, typeToString(type));
                    }
                }
                if (activeSocket != NULL) {
                    numRead = activeSocket->Receive(maxCount);
                    if (numRead > 0) {
                        if (numRead > maxCount) {
                            log->debug(
                        "CLSocketChannel.read received %d/%d bytes from %s:%d:%s",
                                numRead, maxCount,
                                activeSocket->GetClientAddr(),
                                activeSocket->GetClientPort(),
                                typeToString(type));
                            numRead = maxCount;
                        } else {
                            log->debug(
                        "CLSocketChannel.read received %d bytes from %s:%d:%s",
                                numRead,
                                activeSocket->GetClientAddr(),
                                activeSocket->GetClientPort(),
                                typeToString(type));
                        }
                        memcpy(buffer, activeSocket->GetData(), numRead);
                    }
                }
            } else {  // UDP
                numRead = passiveSocket->Receive(maxCount);
                if (numRead > 0) {
                    if (numRead > maxCount) {
                        log->debug(
                        "CLSocketChannel.read received %d/%d bytes from %s:%d:%s",
                            numRead, maxCount,
                            passiveSocket->GetClientAddr(),
                            passiveSocket->GetClientPort(),
                            typeToString(type));
                        numRead = maxCount;
                    } else {
                        log->debug(
                        "CLSocketChannel.read received %d bytes from %s:%d:%s",
                            numRead,
                            passiveSocket->GetClientAddr(),
                            passiveSocket->GetClientPort(),
                            typeToString(type));
                    }
                    memcpy(buffer, passiveSocket->GetData(), numRead);
                }
            }
        }
    }

    return numRead;
}


/**
 ****************************************************************************
 * @brief  Writes to the socket.
 ****************************************************************************
**/
int CLSocketChannel::write(const void *buffer, int count) {
    int numWritten = 0;

    if (mode == ACTIVE) {
        if (activeSocket == NULL) {
            log->warn("CLSocketChannel.write: %s:%d:%s not opened",
                ip_address.c_str(), ip_port, typeToString(type));
        } else {
            numWritten = activeSocket->Send(
                static_cast <const uint8 *> (buffer), count);
            log->debug(
                "CLSocketChannel.write sent %d bytes to %s:%d:%s",
                numWritten, ip_address.c_str(), ip_port, typeToString(type));
       }
    } else {  // PASSIVE
        if (passiveSocket == NULL) {
            log->warn("CLSocketChannel.write: %s:%d:%s not opened",
                ip_address.c_str(), ip_port, typeToString(type));
        } else {
            if (type == TCP) {
                if (activeSocket == NULL) {
                    /*activeSocket =*/ passiveSocket->Accept();
                    if (activeSocket != NULL) {
                        ip_address = reinterpret_cast <char *>
                            (activeSocket->GetClientAddr());
                        ip_port = activeSocket->GetClientPort();
                        log->info(
                    "CLSocketChannel.write accepted new conection from %s:%d:%s",
                    ip_address.c_str(), ip_port, typeToString(type));
                    }
                }
                if (activeSocket != NULL) {
                    numWritten = activeSocket->Send(
                        static_cast <const uint8 *> (buffer), count);
                    log->debug(
                        "CLSocketChannel.write sent %d bytes to %s:%d:%s",
                        numWritten,
                        activeSocket->GetClientAddr(),
                        activeSocket->GetClientPort(),
                        typeToString(type));
                }
            } else {  // UDP
                numWritten = passiveSocket->Send(
                    static_cast <const uint8 *> (buffer), count);
                log->debug(
                    "CLSocketChannel.write sent %d bytes to %s:%d:%s",
                    numWritten,
                    passiveSocket->GetClientAddr(),
                    passiveSocket->GetClientPort(),
                    typeToString(type));
            }
        }
    }

    return numWritten;
}
